using System;
using System.Collections.Generic;
#if(!WindowsCE)
using System.Linq.Expressions;
#endif
using ValidationFramework.Extensions;
using ValidationFramework.Reflection;
using System.Linq;
using ValidationFramework.Utilities;

namespace ValidationFramework
{
	/// <summary>
	/// Base class for field and property validation.
	/// </summary>
	/// <seealso cref="PropertyValidationManager"/>
	/// <seealso cref="FieldValidationManager"/>
	public partial class MemberValidationManager
	{
		// --- Fields

		protected readonly List<ValidationError> _validationErrors;

		private string ruleSet;
		private TypePolicy _typePolicy;
		private readonly Dictionary<string, IValidationPolicy> policyDictionary;


		// --- Constructors

		/// <summary>
		/// Intializes an instance of member validation manager. Use the factory to build one of these.
		/// </summary>
		/// <param name="target"></param>
		/// <param name="policyList"></param>
		/// <remarks>An instance of the object being validated is required for this to work.</remarks>
	    internal MemberValidationManager(object target, IEnumerable<IValidationPolicy> policyList)
		{
			Guard.ArgumentNotNull(target, "target");

			Target = target;
			TargetHandle = target.GetType().TypeHandle;

			policyDictionary = new Dictionary<string, IValidationPolicy>();
			_validationErrors = new List<ValidationError>();

		    foreach (var policy in policyList)
		    {
				policyDictionary.Add(policy.MemberName, policy);
		    }
		}


		// --- Properties

		/// <summary>
		/// Gets a value indicating if all members are valid.
		/// </summary>
		/// <remarks>Calling this property does not perform a validation it only checks the current state. To perform a full validation call 
		/// <see cref="MemberValidationManager{T}.ValidateAll()"/>.</remarks>
		public bool IsValid
		{
			get
			{
				return (_validationErrors.Count == 0);
			}
		}

		public TypePolicy TypePolicy
		{
			get
			{
				return _typePolicy;
			}
		}

		/// <summary>
		/// Gets the instance of the object that this <see cref="MemberValidationManager{T}"/> is handling.
		/// </summary>
		/// <remarks>Will return a null for static types.</remarks>
		public Object Target
		{
			get;
			set;
		}

		/// <summary>
		/// Gets a <see cref="IList{T}"/> containing all <see cref="ValidationError"/>s.
		/// </summary>
		public IList<ValidationError> ValidationErrors
		{
			get
			{
				return _validationErrors;
			}
		}

		/// <summary>
		/// Gets the <see cref="RuntimeTypeHandle"/> for the <see cref="Type"/> being validated.
		/// </summary>
		public RuntimeTypeHandle TargetHandle
		{
			get;
			set;
		}

		/// <summary>
		/// Gets the rule set to validate.
		/// </summary>
		/// <remarks>
		/// Only <see cref="Rule"/>s where RuleSet equals this value.
		/// Case insensitive so this will always return a upper case string no matter what is passed into the constructor.
		/// </remarks>
		/// <exception cref="ArgumentException"><paramref name="value"/> is a <see cref="string.Empty"/>.</exception>
		public string RuleSet
		{
			get
			{
				return ruleSet;
			}
			set
			{
				Guard.ArgumentNotEmptyString(ruleSet, "ruleSet");
				ruleSet = value.ToUpperIgnoreNull();
			}
		}

		/// <summary>
		/// An <see cref="object"/> to pass as the context parameter when calling <see cref="Rule.Validate"/>. The default is null.
		/// </summary>
		public object Context
		{
			get;
			set;
		}


		// --- Methods

		/// <summary>
		/// Get a <see cref="IList{T}"/> of all <see cref="ValidationError"/>s for a given member.
		/// </summary>
		/// <remarks>Use <see cref="ResultFormatter"/> for some basic formatting conversions of <see cref="ValidationError"/>s.</remarks>
		/// <param name="memberName">Member to retrieve error message for. Case sensitive.</param>
		/// <returns>All <see cref="ValidationError"/>s for a given member.</returns>
		/// <exception cref="ArgumentNullException"><paramref name="memberName"/> is null.</exception>
		/// <exception cref="ArgumentException"><paramref name="memberName"/> is <see cref="string.Empty"/>.</exception>
		/// <exception cref="ArgumentException">No <see cref="InfoDescriptor"/> could be found named <paramref name="memberName"/>.</exception>
        public IList<ValidationError> GetResultsInError(string memberName)
		{
		    Guard.ArgumentNotNullOrEmptyString(memberName, "memberName");

			return _validationErrors.Where(x => x.MemberName == memberName).ToList();
		}
#if (!WindowsCE)
		/// <summary>
		/// Get a <see cref="IList{T}"/> of all <see cref="ValidationError"/>s for a given member.
		/// </summary>
		/// <remarks>Use <see cref="ResultFormatter"/> for some basic formatting conversions of <see cref="ValidationResult"/>s.</remarks>
		/// <param name="expression">The <see cref="Expression{TDelegate}"/> to retrieve error message for. </param>
		/// <returns>All <see cref="ValidationErrors"/>s for a given member.</returns>
		/// <exception cref="ArgumentNullException"><paramref name="expression"/> is null.</exception>
		/// <exception cref="ArgumentException">No <see cref="InfoDescriptor"/> could be found based on <paramref name="expression"/>.</exception>
		public IList<ValidationError> GetResultsInError<TDeclaringType>(Expression<Func<TDeclaringType>> expression)
		{
			return GetResultsInError(ExprUtil.GetMemberName(expression));
		}
#endif
		/// <summary>
		/// Get a <see cref="IList{T}"/> of all <see cref="ValidationError"/>s for a list of properties.
		/// </summary>
		/// <remarks>Use <see cref="ResultFormatter"/> for some basic formatting conversions of <see cref="ValidationResult"/>s.</remarks>
		/// <param name="memberNames">The names of the properties to retrieve error messages for. Case sensitive.</param>
		/// <returns>All <see cref="ValidationError"/>s for the list of properties.</returns>
		/// <exception cref="ArgumentNullException"><paramref name="memberNames"/> is null.</exception>
		/// <exception cref="ArgumentOutOfRangeException">Length of <paramref name="memberNames"/> is 0.</exception>
		/// <exception cref="ArgumentException">No <see cref="InfoDescriptor"/> could be found any single member <paramref name="memberNames"/>.</exception>
		public IList<ValidationError> GetResultsInErrorForList(params string[] memberNames)
		{
			Guard.ArgumentNotNull(memberNames, "memberNames");
			if (memberNames.Length == 0)
				throw new ArgumentOutOfRangeException("memberNames");

			return _validationErrors.Where(x => memberNames.Contains(x.MemberName)).ToList();
		}

	    /// <summary>
		/// Populates <see cref="validationErrors"/> with <see cref="ValidationError"/>s for the member being tested.
		/// </summary>
		/// <param name="policy">The <see cref="ValidationPolicy"/> to test.</param>
		/// <returns><see langword="true"/> if a <see cref="Rule"/> is found; otherwise <see langword="false"/>.</returns>
		internal void ValidateMember(IValidationPolicy policy, bool throwException)
		{
			// clear validation errors for this member
			_validationErrors.RemoveAll(x => x.MemberName == policy.MemberName);

			// set validation options
			var options = GetValidationOptions();
			options.ThrowExceptionOnError = throwException;

			// perform validation
			var errors = policy.Validate(Target, options);

			// add any errors for this member to the manager's validation errors
			_validationErrors.AddRange(errors);
		}

		internal void ValidateMemberNoParent(object memberValue, IValidationPolicy policy, bool throwException)
		{
			// clear validation errors for this member
			_validationErrors.RemoveAll(x => x.MemberName == policy.MemberName);

			// set validation options
			var options = GetValidationOptions();
			options.ThrowExceptionOnError = throwException;

			// perform validation
			var errors = policy.ValidateNoParent(memberValue, options);

			// add any errors for this member to the manager's validation errors
			_validationErrors.AddRange(errors);
		}

	    // --- Validate

		/// <summary>
		/// Validates all properties.
		/// </summary>
		/// <exception cref="InvalidOperationException">No <see cref="Rule"/>s can be found.</exception>
		public void ValidateAll()
		{
			_validationErrors.Clear();
			foreach (var kvp in policyDictionary)
			{
				var policy = kvp.Value;
				ValidateMember(policy, false);
			}
		}

		/// <summary>
		/// Validates the specified member.
		/// </summary>
		/// <param name="memberName">Member to validate. Case sensitive.</param>
		/// <exception cref="ArgumentNullException"><paramref name="memberName"/> is a null reference.</exception>
		/// <exception cref="ArgumentException"><paramref name="memberName"/> is <see cref="string.Empty"/>.</exception>
		/// <exception cref="ArgumentException">No <see cref="InfoDescriptor"/> could be found named <paramref name="memberName"/>.</exception>
		/// <exception cref="ArgumentException">No <see cref="Rule"/>s could be found on the <see cref="InfoDescriptor"/>,for <paramref name="memberName"/>, that have the RuleSet equal to <see cref="MemberValidationManager{T}.RuleSet"/>.</exception>
        public void Validate(string memberName)
		{
		    Guard.ArgumentNotNullOrEmptyString(memberName, "memberName");
		    IValidationPolicy policy;
			if (policyDictionary.TryGetValue(memberName, out policy))
		    {
				ValidateMember(policy, false);
		    }
		}
#if (!WindowsCE)
		/// <summary>
		/// Validates the specified member.
		/// </summary>
		/// <param name="expression">The <see cref="Expression{TDelegate}"/> to validate. Case sensitive.</param>
		/// <exception cref="ArgumentNullException"><paramref name="expression"/> is a null reference.</exception>
		/// <exception cref="ArgumentException">No <see cref="InfoDescriptor"/> could be found named <paramref name="expression"/>.</exception>
		/// <exception cref="ArgumentException">No <see cref="Rule"/>s could be found on the <see cref="InfoDescriptor"/>,for <paramref name="expression"/>, that have the RuleSet equal to <see cref="MemberValidationManager{T}.RuleSet"/>.</exception>
        public void Validate<TMember>(Expression<Func<TMember>> expression)
		{
			Validate(ExprUtil.GetMemberName(expression));
		}
#endif
		/// <summary>
		/// Performs validation when a member is being set.
		/// </summary>
		/// <remarks>
		/// <para>Should be called before the field (representing this member) is set.</para>
		/// <para>This should be used if you expect the member to have <see cref="Rule"/>s applied to it. 
		///  This is usually the case if you are using attributes to apply <see cref="Rule"/>s.
		///  If <see cref="Rule"/>s are being assigned using code it may be more appropriate to call <see cref="TryThrowException"/></para> 
		/// </remarks>
		/// <param name="memberName">Member to validate. Case sensitive.</param>
		/// <param name="memberValue">The value of the member being validated.</param>
		/// <param name="context">An <see cref="object"/> to pass as the context parameter when calling <see cref="Rule.Validate"/>. Use a null for a non value.</param>
		/// <exception cref="ArgumentNullException"><paramref name="memberName"/> is null.</exception>
		/// <exception cref="ArgumentException"><paramref name="memberName"/> is <see cref="string.Empty"/>.</exception>
		/// <exception cref="ArgumentException">No <see cref="InfoDescriptor"/> could be found named <paramref name="memberName"/>.</exception>
		public void ThrowException(object memberValue, string memberName)
		{
			Guard.ArgumentNotNullOrEmptyString(memberName, "memberName");
		    IValidationPolicy policy;
		    if (policyDictionary.TryGetValue(memberName, out policy))
		    {
                ValidateMemberNoParent(memberValue, policy, true);   
		    }
		}
#if (!WindowsCE)
		/// <summary>
		/// Performs validation when a member is being set.
		/// </summary>
		/// <remarks>
		/// <para>Should be called before the field (representing this member) is set.</para>
		/// </para> 
		/// </remarks>
		/// <param name="expression">The <see cref="Expression{TDelegate}"/> to validate.</param>
		/// <param name="memberValue">The value of the member being validated.</param>
		/// <param name="context">An <see cref="object"/> to pass as the context parameter when calling <see cref="Rule.Validate"/>. Use a null for a non value.</param>
		/// <exception cref="ArgumentNullException"><paramref name="expression"/> is null.</exception>
		/// <exception cref="ArgumentException">No <see cref="InfoDescriptor"/> could be found based on <paramref name="expression"/>.</exception>
		public void ThrowException<TDeclaringType>(object memberValue, Expression<Func<TDeclaringType>> expression)
		{
			ThrowException(memberValue, ExprUtil.GetMemberName(expression));
		}
#endif

		private ValidationOptions GetValidationOptions()
		{
			return new ValidationOptions() { RuleSet = ruleSet };
		}
	}
}